//
// Created by Administrator on 2021/9/2.
//

#include "Sbm/Object.h"

#include <fstream>

namespace SBM
{
    void Object::Load(const char* filename)
    {
        // free old
        this->Free();

        // read data from file
        std::ifstream ifs(filename, std::ifstream::in | std::ifstream::binary);
        ifs.seekg(0, std::ifstream::end);
        auto size = ifs.tellg();
        ifs.seekg(0, std::ifstream::beg);

        char* data = new char[size];
        ifs.read(data, size);

        // parse
        char* ptr = data;
        auto* header = reinterpret_cast<SBM_HEADER*>(ptr);
        ptr += header->size;

        SBM_VERTEX_ATTRIB_CHUNK* vertexAttribChunk = nullptr;
        SBM_CHUNK_VERTEX_DATA* vertexDataChunk = nullptr;
        SBM_CHUNK_INDEX_DATA* indexDataChunk = nullptr;
        SBM_CHUNK_SUB_OBJECT_LIST* subObjectChunk = nullptr;
        SBM_DATA_CHUNK* dataChunk = nullptr;

        for(unsigned int i = 0; i < header->numChunks; ++i)
        {
            auto* chunk = reinterpret_cast<SBM_CHUNK_HEADER*>(ptr);
            ptr += chunk->size;
            switch (chunk->type)
            {
                case SBM_CHUNK_TYPE_VERTEX_ATTRIBS:
                    vertexAttribChunk = reinterpret_cast<SBM_VERTEX_ATTRIB_CHUNK*>(chunk);
                    break;
                case SBM_CHUNK_TYPE_VERTEX_DATA:
                    vertexDataChunk = reinterpret_cast<SBM_CHUNK_VERTEX_DATA*>(chunk);
                    break;
                case SBM_CHUNK_TYPE_INDEX_DATA:
                    indexDataChunk = reinterpret_cast<SBM_CHUNK_INDEX_DATA*>(chunk);
                    break;
                case SBM_CHUNK_TYPE_SUB_OBJECT_LIST:
                    subObjectChunk = reinterpret_cast<SBM_CHUNK_SUB_OBJECT_LIST*>(chunk);
                    break;
                case SBM_CHUNK_TYPE_DATA:
                    dataChunk = reinterpret_cast<SBM_DATA_CHUNK*>(chunk);
                    break;
                default:
                    break;
            }
        }

        glCreateVertexArrays(1, &vao_);
        glBindVertexArray(vao_);

        if(dataChunk)
        {
            glCreateBuffers(1, &dataBuffer_);
            glBindBuffer(GL_ARRAY_BUFFER, dataBuffer_);
            glBufferData(
                    GL_ARRAY_BUFFER,
                    dataChunk->dataLength,
                    reinterpret_cast<unsigned char*>(dataChunk) + dataChunk->offset,
                    GL_STATIC_DRAW);
        }
        else
        {
            unsigned int dataSize = 0;
            unsigned int sizeUsed = 0;
            if(vertexDataChunk)
            {
                dataSize += vertexDataChunk->size;
            }
            if(indexDataChunk)
            {
                dataSize += indexDataChunk->count *
                        (indexDataChunk->type == GL_UNSIGNED_SHORT ? sizeof(GLushort) : sizeof(GLubyte));
            }

            glCreateBuffers(1, &dataBuffer_);
            glBindBuffer(GL_ARRAY_BUFFER, dataBuffer_);
            glBufferData(GL_ARRAY_BUFFER, dataSize, nullptr, GL_STATIC_DRAW);
            if(vertexDataChunk)
            {
                glBufferSubData(GL_ARRAY_BUFFER, 0, vertexDataChunk->size, data + vertexDataChunk->offset);
                sizeUsed += vertexDataChunk->offset;
            }
            if(indexDataChunk)
            {
                glBufferSubData(
                        GL_ARRAY_BUFFER,
                        sizeUsed,
                        indexDataChunk->count *
                        (indexDataChunk->type == GL_UNSIGNED_SHORT ? sizeof(GLushort) : sizeof(GLubyte)),
                        data + indexDataChunk->dataOffset);
            }
        }

        for(auto i = 0; i < vertexAttribChunk->count; ++i)
        {
            auto& attribDecl = vertexAttribChunk->data[i];
            glVertexAttribPointer(
                    i,
                    attribDecl.size,
                    attribDecl.type,
                    attribDecl.flags & SBM_VERTEX_ATTRIB_FLAG_NORMALIZED ? GL_TRUE : GL_FALSE,
                    attribDecl.stride,
                    reinterpret_cast<GLvoid*>(static_cast<uintptr_t>(attribDecl.dataOffset))
                    );
            glEnableVertexAttribArray(i);
        }

        if(indexDataChunk)
        {
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dataBuffer_);
            indexType_ = indexDataChunk->type;
            indexOffset_ = indexDataChunk->dataOffset;
        }
        else
        {
            indexType_ = GL_NONE;
        }

        if(subObjectChunk != nullptr)
        {
            if(subObjectChunk->count > MAX_SUB_OBJECTS)
            {
                subObjectChunk->count = MAX_SUB_OBJECTS;
            }

            for(auto i = 0; i < subObjectChunk->count; ++i)
            {
                subObjects_[i] = subObjectChunk->subObject[i];
            }

            numSubObjects_ = subObjectChunk->count;
        }
        else
        {
            subObjects_[0].first = 0;
            subObjects_[0].count = indexType_ != GL_NONE ? indexDataChunk->count : vertexDataChunk->totalVertices;
            numSubObjects_ = 1;
        }

        delete[] data;

        ifs.close();

        glBindVertexArray(0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    }

    void Object::Free()
    {
        glDeleteVertexArrays(1, &vao_);
        glDeleteBuffers(1, &dataBuffer_);

        vao_ = 0;
        dataBuffer_ = 0;
    }

    void Object::RenderSubObject(unsigned int objectIndex, unsigned int instanceCount, unsigned int baseInstance)
    {
        glBindVertexArray(vao_);
        if(indexType_)
        {
            glDrawElementsInstancedBaseInstance(
                    GL_TRIANGLES,
                    subObjects_[objectIndex].count,
                    indexType_,
                    reinterpret_cast<void*>(subObjects_[objectIndex].first),
                    instanceCount,
                    baseInstance);
        }
        else
        {
            glDrawArraysInstancedBaseInstance(
                    GL_TRIANGLES,
                    subObjects_[objectIndex].first,
                    subObjects_[objectIndex].count,
                    instanceCount,
                    baseInstance);
        }
    }
}